package com.bjb.vr.remote.ws.websocket.websocket;

import com.alibaba.fastjson.JSONObject;
import com.bjb.vr.common.constant.ThreadLocalConstant;
import com.bjb.vr.common.constant.TokenConstants;
import com.bjb.vr.common.dto.ResGroup;
import com.bjb.vr.common.result.BaseErrorCode;
import com.bjb.vr.common.result.BaseResult;
import com.bjb.vr.common.service.ResGroupService;
import com.bjb.vr.common.utils.JwtTokenUtil;
import com.bjb.vr.common.utils.RedisUtil;
import com.bjb.vr.common.utils.ThreadLocalUtil;
import com.bjb.vr.remote.ws.rabbitmq.config.IpConfiguration;
import com.bjb.vr.remote.ws.rabbitmq.constants.MessageActionConstants;
import com.bjb.vr.remote.ws.rabbitmq.constants.RabbitmqConstants;
import com.bjb.vr.remote.ws.rabbitmq.entity.RabbitWebSocketMsg;
import com.bjb.vr.remote.ws.rabbitmq.utils.MQUtil;
import com.bjb.vr.remote.ws.redis.constants.RedisConstants;
import com.bjb.vr.remote.ws.websocket.constants.WebSocketConstants;
import com.bjb.vr.remote.ws.websocket.entity.*;
import io.netty.handler.codec.http.HttpHeaders;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.yeauty.annotation.*;
import org.yeauty.pojo.Session;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@ServerEndpoint(path = "/ws/param",host = "${ws.host}",port = "${ws.port}")
@Component
@Slf4j
public class WebSocketServer {
    private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);

    //是否成功加入分组
    private boolean isConnect;

    //分组号
    private String groupCode;

    //设备标识
    private String deviceId;

    //连接状态 0 连接中  1 已连接 2 已关闭 3 出错
    private int state;

    public Session session;

    private String isAdmin;

    //心跳标识
    private boolean isHeart = false;

    private boolean kick = false;

    //租户ID
    private String tenantId;

    //下次发送时间的时间戳
    private long heartTime;

    private static ApplicationContext applicationContext;

    private ResGroupService resGroupService;

    private RedisUtil redisUtil;

    private IpConfiguration ipConfiguration;

    /**
     * 当前活跃分组列表
     */
    public  static Map<String, Map<String, WebSocketServer>> classMap = new ConcurrentHashMap<String, Map<String, WebSocketServer>>();


    public static void setApplicationContext(ApplicationContext applicationContext) {
        WebSocketServer.applicationContext = applicationContext;
    }

    /**
     * 连接建立成功调用的方法
     *
     * @param session
     *            可选的参数。session为与某个客户端的连接会话，需要通过它来给客户端发送数据
     * @throws IOException
     */
    @OnOpen
    public void onOpen(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap) throws IOException{



        this.session = session;

        List<String> groupCodeValues = ((LinkedMultiValueMap<String, String>) reqMap).get("groupCode");
        List<String> deviceIdValues = ((LinkedMultiValueMap<String, String>) reqMap).get("deviceId");
        List<String> isAdminValues = ((LinkedMultiValueMap<String, String>) reqMap).get("isAdmin");
        List<String> tokenValues = ((LinkedMultiValueMap<String, String>) reqMap).get("token");
        String _groupCode = groupCodeValues!=null?groupCodeValues.get(0):null;
        this.deviceId = deviceIdValues!=null?deviceIdValues.get(0):null;
        this.isAdmin = isAdminValues!=null?isAdminValues.get(0):null;
        String token = tokenValues!=null?tokenValues.get(0):null;
        if(!StringUtils.isEmpty(token)){
            String tenantId = (String) JwtTokenUtil.parseJWT(token).get(TokenConstants.TENANT_ID);
            this.tenantId = tenantId;
        }

//        this.session.setMaxTextMessageBufferSize(4 * 1024 * 1024);
//        this.session.setMaxBinaryMessageBufferSize(32 * 1024 * 1024);
//        this.session.setMaxIdleTimeout(3600 * 1000);

        resGroupService = applicationContext.getBean(ResGroupService.class);
        redisUtil = applicationContext.getBean(RedisUtil.class);
        ipConfiguration = applicationContext.getBean(IpConfiguration.class);

        BaseResult<ResGroup> result = null;
        ThreadLocalUtil.set(ThreadLocalConstant.NO_TENANT_ID_VERIFY, true);
        if(isAdmin()) {
            result = resGroupService.checkGroupCode(_groupCode);
        }else{
            result = resGroupService.checkclientGroup(deviceId);
        }

        if(!result.isSuccess())
        {
            logger.error("连接拒绝 " +  " [isAdmin] " + isAdmin  + " [groupCode] " + groupCode + " [deviceId]" + deviceId + " [连接拒绝原因] " + result.getMessage());
            sendConnStatus(BaseErrorCode.REFUSE.getCode(),result.getMessage());
            return;
        }


        if(isAdmin()){
            this.deviceId = _groupCode;
            this.groupCode = _groupCode;
        }else{
            this.groupCode = result.getData().getGroupCode();
        }

        String stateKey = RedisConstants.WEBSOCKET_CLIENT_ID + groupCode + "-" + deviceId;
        //检测设备是否在连接中,设备状态是onopen状态不允许连接
//        Integer _state = (Integer) redisUtil.get(stateKey);
//        if(_state!=null  && (_state == WebSocketConstants.WEBSOCKET_CLIENT_STATE_ONOPEN || _state == WebSocketConstants.WEBSOCKET_CLIENT_STATE_OPENED))
//        {
//            String errorMsg = "";
//            if(isAdmin()){
//                errorMsg = WebSocketConstants.MSG_ERROR_ADMIN_ON_OPEN;
//            }else{
//                errorMsg = WebSocketConstants.MSG_ERROR_CLIENT_ON_OPEN;
//            }
//
//            logger.error("连接拒绝 " +  " [isAdmin] " + isAdmin  + " [groupCode] " + groupCode + " [deviceId]" + deviceId + " [连接拒绝原因] " + errorMsg);
//            sendConnStatus(BaseErrorCode.REFUSE.getCode(),errorMsg);
//            return;
//        }
        this.state = WebSocketConstants.WEBSOCKET_CLIENT_STATE_ONOPEN;
        redisUtil.set(stateKey ,this.state, RedisConstants.WEBSOCKET_CLIENT_STATE_KEY_EXPIRE_TIME);
        String ip_port = ipConfiguration.getIp() + "_" + ipConfiguration.getPort();
        String ip_port_client_key = RedisConstants.WEBSOCKET_IP_PORT_CLIENT_IDS + ip_port;
        //保存重启需要删除的客户端连接id缓存
        redisUtil.sSetAndTime(ip_port_client_key,RedisConstants.WEBSOCKET_CLIENT_STATE_KEY_EXPIRE_TIME,stateKey);
        joinRoom(groupCode,deviceId);
        if(logger.isDebugEnabled()) {
            logger.debug("序列号:" + session.id() + ",分组:" + groupCode
                    + ",设备:" + deviceId + ",isConnect:" + isConnect
                    + ",num:" + classMap.get(groupCode).size() + ",连接成功!");
        }

        if(isConnect)
        {
            sendConnStatus(BaseErrorCode.SUCCESS.getCode(),WebSocketConstants.MSG_SUCC_DEVICE_CONNECTED);
        }
    }


    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     *            客户端发送过来的消息
     * @param session
     *            可选的参数
     * @throws IOException
     */
    @OnMessage
    public void OnMessage(Session session, String message) throws IOException
    {
        if(logger.isDebugEnabled())
        {
            logger.debug(String.valueOf("设备列表:" + classMap.get(groupCode) != null ? classMap.get(groupCode).keySet() : null));
            logger.debug("序列号:" + session.id() + ",分组:" + groupCode + ",设备:"
                    + deviceId + ",isConnect:" + isConnect + " 发送消息：" + message);
        }
//        logger.info("收到客户端消息 " + message + " groupCode " + groupCode  + " deviceId " + deviceId);
        JSONObject jsonObject = JSONObject.parseObject(message);
        Action action = JSONObject.toJavaObject(jsonObject,Action.class);
        BeatTypeParameter beatTypeParameter = JSONObject.toJavaObject((JSONObject)action.getCommand().getParameter(),BeatTypeParameter.class);

        if(MessageActionConstants.MESSAGE_ACTION_BEAT_HEART.equals(action.getAction()) &&
                MessageActionConstants.CLIENT_BEAT_TYPE.equals(beatTypeParameter.getBeatType())){
                isHeart = true;
        }else {
            sendRabbitmqTextMsg(message);
        }

    }


    /**
     * 连接关闭调用的方法
     *
     * @param session
     * @throws IOException
     */
    @OnClose
    public void onClose(Session session) throws IOException
    {
        logger.info("调用 onClose "  + this.deviceId );
        disconnect();
    }

    /**
     * 发生错误时调用
     *
     * @param session
     * @param error
     * @throws IOException
     */
    @OnError
    public void onError(Session session, Throwable error) throws IOException
    {
        logger.info("调用 onError "  + this.deviceId );
        if(logger.isDebugEnabled()) {
            logger.debug("序列号:" + session.id() + ",分组:" + groupCode + ",设备:"
                    + deviceId + ",isConnect:" + isConnect + " 连接错误");
        }
        disconnect();
        error.printStackTrace();
    }


    /**
     * 连接状态响应
     *
     * @throws IOException
     */
    public void sendConnStatus(String code ,String message)
            throws IOException
    {

        Action action = new Action();
        action.setAction(MessageActionConstants.MESSAGE_ACTION_CONNECT_RESP);
        Command<ConnectionRespParameter> command = new Command<>();
        ConnectionRespParameter parameter = new ConnectionRespParameter();
        parameter.setCode(code);
        parameter.setMessage(message);
        command.setParameter(parameter);
        action.setCommand(command);
        sendMsg(JSONObject.toJSONString(action));
        if(!BaseErrorCode.SUCCESS.getCode().equals(code))
        {
            if(session.isOpen()) {
                session.close();
            }
        }
    }


    private void sendRabbitmqTextMsg(String msg){

        RabbitWebSocketMsg rwsc = new RabbitWebSocketMsg();
        rwsc.setGroupCode(this.groupCode);
        rwsc.setDeviceId(this.deviceId);
        rwsc.setIsAdmin(this.isAdmin);
        rwsc.setTenantId(this.tenantId);
        rwsc.setMsgType(WebSocketConstants.WEBSOCKET_CLIENT_MSG_TYPE_TEXT);
        rwsc.setMsg(msg);
        String json = JSONObject.toJSONString(rwsc);
        //设置msg rwsc 为空
        msg = null;
        rwsc = null;
        MQUtil.send(RabbitmqConstants.FANOUT_WEBSOCKET_EXCHANGE,"", json);

    }

    private void sendDevOnlineStateMsg(int onlineState){
        String msg = "";
        Action action = new Action();
        action.setAction(MessageActionConstants.MESSAGE_ACTION_CVR_ONLINE_STATE);
        Command<DevOnlineStateParameter>  command = new Command<>();
        command.setUdid(this.deviceId);
        DevOnlineStateParameter parameter = new DevOnlineStateParameter();
        parameter.setOnlineState(onlineState);
        parameter.setKick(this.kick);
        command.setParameter(parameter);
        action.setCommand(command);
        msg = JSONObject.toJSONString(action);

        sendRabbitmqTextMsg(msg);
    }


    /**
     * 加入分组
     *
     * @param code
     * @param deviceId
     */
    public void joinRoom(String code,String deviceId)
    {
        Map<String, WebSocketServer> room = classMap.get(code);
        if(room == null)
        {
            room = new ConcurrentHashMap<String, WebSocketServer>();
            classMap.put(code, room);
        }
        room.put(deviceId, this);
        setClientOpened();
    }

    /**
     * @param msg
     * @throws IOException
     */
    public void sendMsg(String msg) throws IOException
    {
        synchronized(session)
        {
            logger.info(" session  " + session.toString() + " msg " + msg);
            session.sendText(msg);
            msg = null;
        }
    }


    public boolean isActive()
    {
        return isConnect && session.isOpen() ? true : false;
    }


    /**
     * 判断连接类型，roomId等于deviceId表示该连接为管理端，否则为客户端
     *
     * @return
     */
    public boolean isAdmin()
    {
        return "1".equals(isAdmin) ? true : false;
    }


    public void setClientOpened(){
        this.isConnect = true;
        String stateKey = RedisConstants.WEBSOCKET_CLIENT_ID + groupCode + "-" + deviceId;
        String redisGroupCode = RedisConstants.WEBSOCKET_GROUP_CODE + this.groupCode;
        this.state = WebSocketConstants.WEBSOCKET_CLIENT_STATE_OPENED;
        redisUtil.set(stateKey ,WebSocketConstants.WEBSOCKET_CLIENT_STATE_OPENED,RedisConstants.WEBSOCKET_CLIENT_STATE_KEY_EXPIRE_TIME);
        if(!this.isAdmin()){
            //组map添加设备id
            redisUtil.sSetAndTime(redisGroupCode,RedisConstants.WEBSOCKET_CLIENT_STATE_KEY_EXPIRE_TIME,this.deviceId);
        }
        //设备上线通知
        this.sendDevOnlineStateMsg(WebSocketConstants.WEBSOCKET_CLIENT_ONLINE_STATE_ON);
    }
    /**
     * 断开连接
     *
     * @throws IOException
     */
    public void disconnect() throws IOException
    {
        logger.info("调用 disconnect "  + this.deviceId );
        if(isConnect)
        {
            String stateKey = RedisConstants.WEBSOCKET_CLIENT_ID + groupCode + "-" + deviceId;
            String redisGroupCode = RedisConstants.WEBSOCKET_GROUP_CODE + this.groupCode;

            if(logger.isDebugEnabled()) {
                logger.debug("序列号:" + session.id() + ",分组:" + groupCode
                        + ",设备:" + deviceId + ",isConnect:" + isConnect
                        + " 关闭设备");
            }
            isConnect = false;
            session.close();
            //设备离线通知
//            if(!this.isAdmin()){
             this.sendDevOnlineStateMsg(WebSocketConstants.WEBSOCKET_CLIENT_ONLINE_STATE_OFF);
//            }

            //设置ws客户端连接状态为关闭
            //redisService.set(stateKey ,WebSocketConstants.WEBSOCKET_CLIENT_STATE_CLOSED,RedisConstants.WEBSOCKET_CLIENT_STATE_KEY_EXPIRE_TIME);
            redisUtil.del(stateKey);
            //分组客户端map移除客户端
            redisUtil.setRemove(redisGroupCode,this.deviceId);
            WebSocketServer webSocketServer = classMap.get(groupCode).get(deviceId);
            webSocketServer = null;
            classMap.get(groupCode).remove(deviceId);


        }
    }

    public static void   close(String groupCode,String deviceId) throws IOException
    {
        WebSocketServer webSocketServer = classMap.get(groupCode).get(deviceId);
        webSocketServer.session.close();

    }


    public static void startHeart(int stuHeartPeriodSecond){
        ExamineHeartThread examineHeart =new ExamineHeartThread();
        Thread examineThread = new Thread(examineHeart);

        StuKeepHeartThread stuKeepHeart= new StuKeepHeartThread();
        stuKeepHeart.setHeartPeriodSeconds(stuHeartPeriodSecond);
        Thread stuKeepThread = new Thread(stuKeepHeart);

        stuKeepThread.start();
        examineThread.start();

    }




    /**
     * 解析ws请求参数
      * @param str
     * @return
     */
    private Map<String,String> paramsParse(String str){
        Map paramsMap = new HashMap<String,String>();
        String[] str1 = str.split("&");
        if(str1!=null && str1.length>0){
            for(String _str:str1){
                String[] str2 = _str.split("=");
                if(str2!=null && str2.length>0 && str2.length ==2){
                    paramsMap.put(str2[0],str2[1]);
                }
            }
        }
        return paramsMap;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public String getGroupCode() {
        return groupCode;
    }

    public void setgroupCode(String groupCode) {
        this.groupCode = groupCode;
    }

    public String getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }


    public void setHeart(boolean heart) {
        isHeart = heart;
    }

    public boolean isKick() {
        return kick;
    }

    public void setKick(boolean kick) {
        this.kick = kick;
    }

    public boolean isHeart() {
        return isHeart;
    }

    public long getHeartTime() {
        return heartTime;
    }

    public void setHeartTime(long heartTime) {
        this.heartTime = heartTime;
    }
}
